package org.vectrics.graphics;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.vectrics.SystemException;
import org.vectrics.VectricsException;
import org.vectrics.common.graphics.Graphic;
import org.vectrics.common.util.FileUtil;
import org.vectrics.domain.Domain;
import org.vectrics.struts.Request;

/**
 * @author mmoore
 */
public class DynaGraphicsManager {
    private DynaGraphicsConfig config = null;

    private File configFile = null;

    private long configFileModifyTime = 0;

    private String configFileName = null;

    private final Logger log = Logger.getLogger(this.getClass());

    public DynaGraphicsManager() {
    }

    public BufferedImage createBufferedImage(Graphic graphic) {
        return graphic.fill();
    }

    public String findStoredImagePath() {
        String storedImagePath = this.config.getResourcesPath() + "/stored";
        return (storedImagePath);
    }

    public String getDefaultConfigurationCacheDirectory() {
        return this.config.getResourcesPath();
    }

    public DynaGraphic getImage(String imageName, Locale locale, Map parameters) {
        DynaGraphic graphic = null;
        ImageConfiguration imageConfig = this.getImageConfig(imageName, locale, parameters);

        if (graphic == null) {
            graphic = new DynaGraphic(imageConfig, locale, parameters);
        }
        return (graphic);
    }

    public ImageConfiguration getImageConfig(String imageName, Locale locale, Map parameters) {
        Map map = this.config.getImageConfigMap();
        ImageConfiguration imageConfig = (ImageConfiguration) map.get(imageName);
        if (imageConfig == null) {
            throw new SystemException("Could not find image named: " + imageName + "   Config file name: "
                    + this.configFileName);
        }
        return (imageConfig);
    }

    public void loadDefinitions(File file) {
        String fileName = file.getName();
        this.log.info("Loading dyna-images from config file: " + fileName);

        if (file.exists() == false) {
            throw new SystemException("Could not find config file: " + fileName);
        }

        this.configFileModifyTime = file.lastModified();
        this.configFileName = fileName;
        this.configFile = file;
        InputStream contents = null;
        try {
            contents = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            this.log.error("Exception not parsing : " + fileName, e);
            throw new SystemException("Exception not parsing : " + fileName, e);
        }

        try {
            this.config = new DynaGraphicsConfig(contents);
        } catch (Exception ex) {
            throw new SystemException("Exeption setting up dyna graphic configuration, file = " + fileName, ex);
        }
    }

    /*
     * private String getDynaConfigFileName(Domain domain) { String fileName =
     * CoreServiceLocator.getConfiguration().getHtmlStyleDirectory(); if
     * (fileName.endsWith("/") == false) { fileName += "/"; } fileName +=
     * domain.getId().toString() + "/" + "";
     * FileUtil.createDirectoryIfNotExist(fileName);
     * 
     * fileName += "/main.css"; }
     */
    public void loadDefinitions(InputStream contentStream) {
        String contents = FileUtil.getContentFromStream(contentStream);
        this.config = new DynaGraphicsConfig(contents);

        this.log.debug("Create path if it doesn't exist.");
        File dir = new File(this.config.getResourcesPath() + "/stored");
        try {
            dir.mkdirs();
        } catch (Exception ex) {
            throw new SystemException("Exception creating directory: " + this.config.getResourcesPath());
        }

    }

    /**
     * @deprecated User loadDefinitions(File) instead.
     */
    @Deprecated
    public void loadDefinitions(String fileName) {
        this.log.info("Loading dyna-images from config file: " + fileName);
        // File file = new File(fileName);
        URL url = null;
        URLConnection urlConnection = null;
        try {
            url = new URL(fileName);
            urlConnection = url.openConnection();
        } catch (MalformedURLException ex) {
            this.log.error("Mal Formada la URL: " + fileName, ex);
            throw new SystemException("Mal Formada la URL: " + fileName, ex);
        } catch (IOException ex) {
            this.log.error("Exception opening URL: " + fileName, ex);
            throw new SystemException("Exception opening file: " + fileName, ex);
        }
        /*
         * if (file.exists() == false) throw new
         * SystemException("Could not find config file: " + fileName);
         */
        this.configFileModifyTime = urlConnection.getLastModified();
        this.configFileName = fileName;
        InputStream contents = null;
        try {
            contents = urlConnection.getInputStream();
        } catch (IOException ex) {
            this.log.error("Exception not parsing : " + fileName, ex);
            throw new SystemException("Exception not parsing : " + fileName, ex);
        }

        try {
            this.config = new DynaGraphicsConfig(contents);
        } catch (Exception ex) {
            throw new SystemException("Exeption setting up dyna graphic configuration, file = " + fileName, ex);
        }
    }

    /**
     * 
     * @param client
     * @param quality
     * @param graphic
     */
    public void saveImageToStoredDirectory(DynaGraphicClient client, float quality, DynaGraphic graphic) {
        String storedFilePath = this.findImagePath(client, graphic);
        GraphicFileWriter writer = new GraphicFileWriter();
        writer.saveImageToFile(storedFilePath, quality, graphic);
    }

    /**
     * Process the HTTP Get request
     **/
    public void service(Domain domain, DynaGraphicClient client, Request request, HttpServletResponse response)
            throws ServletException, IOException {
        this.log.debug("service() - handling request for PDF report");

        if (this.log.isDebugEnabled()) {
            String awtToolKit = System.getProperty("awt.toolkit");
            String awtGraphicEnv = System.getProperty("java.awt.graphicsenv");
            String awtFont = System.getProperty("java.awt.fonts");
            this.log.debug("GRAPHICS: awt.toolkit = " + awtToolKit);
            this.log.debug("GRAPHICS: java.awt.graphicsenv = " + awtGraphicEnv);
            this.log.debug("GRAPHICS: java.awt.fonts = " + awtFont);
        }

        this.reloadIfConfigUpdated();
        byte[] responseBytes;
        try {
            InitialContext context = new InitialContext();
            Enumeration envEnum = context.getEnvironment().elements();
            while (envEnum.hasMoreElements()) {
                envEnum.nextElement();
            }
        } catch (Exception ex) {
            this.log.error("Exception getting initial context", ex);
        }

        String reportParam = request.getParameter("report");
        this.log.debug("Report param = " + reportParam);
        try {
            // Date now = new Date();
            // long time = now.getTime();
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            this.makeGraphic(client, request, outStream);

            if (this.log.isDebugEnabled()) {
                this.log.debug("Output stream length = " + outStream.size());
                this.log.debug("Converting output stream to byte array");
            }
            responseBytes = outStream.toByteArray();

        } catch (VectricsException ve) {
            throw new ServletException("VectricsException building graphic", ve);
        } catch (Exception ex) {
            throw new ServletException("Exception building report", ex);
        }

        this.log.debug("Setting content type");
        response.setContentType("image/jpeg");

        if (this.log.isDebugEnabled()) {
            this.log.debug("Writing bytes to response");
            this.log.debug("Size of byte array = " + responseBytes.length);
        }
        response.getOutputStream().write(responseBytes);
        response.setContentLength(responseBytes.length);

        this.log.debug("Flushing response");
        response.getOutputStream().flush();
        this.log.debug("Done");
    }

    void makeGraphic(DynaGraphicClient client, Request request, OutputStream out) throws VectricsException {
        float quality = 0.95f;
        String imageCode = request.getParameter("code");
        Locale locale = request.getLocale();
        if (locale == null) {
            locale = Locale.US;
        }

        Map parameters = new HashMap();
        Iterator<String> paramNames = request.getParameterNames().iterator();
        while (paramNames.hasNext()) {
            String paramName = paramNames.next();
            String paramValue = request.getParameter(paramName);
            parameters.put(paramName, paramValue);
        }

        // ImageConfiguration imageConfig = this.getImageConfig(imageCode,
        // locale, parameters);
        DynaGraphic graphic = this.getImage(imageCode, locale, parameters);
        InputStream storeImageStream = this.findStoredImage(client, graphic);
        if (storeImageStream != null) {
            this.writeFileToOutputStream(storeImageStream, out);
        } else {
            try {
                GraphicFileWriter.writeGraphicAsJPEG(out, quality, graphic);
                this.saveImageToStoredDirectory(client, quality, graphic);
            } catch (Exception ex) {
                this.log.error("Exception occurred while converting image to jpeg", ex);
            } finally {
                try {
                    if (storeImageStream != null) {
                        storeImageStream.close();
                    }
                } catch (Exception ex) {
                    this.log.warn("Exception closing file stream", ex);
                }
            }
        }
    }

    private String findImagePath(DynaGraphicClient client, DynaGraphic graphic) {
        String cacheDirectory = client.getCacheDirectory();

        this.log.debug("Create cache directory if it does not exist: " + cacheDirectory);
        FileUtil.createDirectoryIfNotExist(cacheDirectory);

        String storedFilePath = cacheDirectory + "/" + graphic.getUniqueName() + ".jpg";
        return (storedFilePath);
    }

    private InputStream findStoredImage(DynaGraphicClient client, DynaGraphic graphic) {
        String filePath = this.findImagePath(client, graphic);

        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(filePath);
        } catch (FileNotFoundException fnfe) {
            inputStream = null;
        }
        return (inputStream);
    }

    private void reloadIfConfigUpdated() {
        if (this.configFile != null) {
            if (this.configFile.lastModified() > this.configFileModifyTime) {
                this.log.info("Dynagraphic config file changed, reloading...");
                this.loadDefinitions(this.configFile);
            } else {
                this.log.debug("No config file change");
            }
        }
    }

    /*
     * Writing input file to response
     */
    private void writeFileToOutputStream(InputStream storeImageStream, OutputStream out) {
        boolean done = false;
        try {
            while (!done) {
                int c = storeImageStream.read();
                if (c >= 0) {
                    out.write(c);
                } else {
                    done = true;
                }
            }
        } catch (IOException ioe) {
            throw new SystemException("IOException writing input file to response", ioe);
        }
    }

}
